﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using OpenServe.Net.Library.Extentions;
using OpenServe.Net.Messaging;
using OpenServe.Net.Library.Interfaces;
using System.Threading.Tasks;
using System.Threading;
using OpenServe.Net.Messaging.Client;
using OpenServe.Net.Library.Utils;
using System.Collections.Concurrent;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting;
using IronPython.Hosting;
using System.IO;


namespace OpenServe.Net.Client {
    public class Client {
        public Guid GUID { get; set; }
        public string Name { get; set; }
        private ScriptEngine engine = Python.CreateEngine();
        private ScriptScope scope = null;
        private bool _secure = false;
        public bool Secure {
            get { return _secure; }
            set { _secure = value; }
        }

        private TcpClient client;
        private dynamic Stream { get; set; }
        public DefaultHandler DefaultHandler;
        public List<IMsgHandler> Handlers = new List<IMsgHandler>();
        private CancellationTokenSource cts = new CancellationTokenSource();
        private StringBuilder sb = new StringBuilder();
        private BlockingCollection<Message> queue = new BlockingCollection<Message>();
        private ManualResetEvent readReset = new ManualResetEvent(false);
        private byte[] buffer = new byte[Utils.BufferSize];
        public byte[] Buffer {
            get { return buffer; }
            set { buffer = value; }
        }
        public Client() {
            // Scripting
            if (!Directory.Exists("Scripts")) Directory.CreateDirectory("Scripts");
            scope = engine.CreateScope();
            // Script Scope
            scope.SetVariable("clnt", this);
            scope.SetVariable("env", "client");
            DefaultHandler = new DefaultHandler(this);
            client = new TcpClient();
            this.AddHandler(DefaultHandler);
            Task.Factory.StartNew(() => {
                while (!cts.IsCancellationRequested) {
                    var msg = queue.Take(cts.Token);
                    if (cts.IsCancellationRequested) break;
                    var b = (msg.ToJson() + Utils.Delimiter).ToBytes();
                    Stream.Write(b, 0, b.Length);
                }
            }, cts.Token);
        }
        ~Client() {
            cts.Cancel();
        }

        public bool Connect(string host, int port) {
            client.Connect(port: port, hostname: host);
            if (Secure) {
                Stream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(CertificateValidationCallback));
                try { Stream.AuthenticateAsClient("OpenServe.Net"); } catch { return false; }
            } else Stream = client.GetStream();
            Task.Factory.StartNew(() => {
                while (!cts.Token.IsCancellationRequested) {
                    try {
                        readReset.Reset();
                        var stream = Stream as Stream;
                        stream.BeginRead(Buffer, 0, Utils.BufferSize, (ar) => {
                            var read = Stream.EndRead(ar);
                            sb.Append(Buffer.BToString());
                            if (read < Utils.BufferSize) {
                                var msgs = sb.ToString().Split(new[] { Utils.Delimiter }, StringSplitOptions.RemoveEmptyEntries).ToList();
                                msgs.ForEach(m => {
                                    var msg = new Message(m);
                                    Handlers.ForEach(h => Task.Factory.StartNew(hd => { var mh = hd as IMsgHandler; mh.HandleMessage(msg); }, h, cts.Token));
                                    // Run Scripts
                                    Directory.GetFiles("Scripts", "*.py").ToList().ForEach(script => {
                                        try {
                                            scope.SetVariable("msg", msg.Clone());
                                            var source = engine.CreateScriptSourceFromFile(script);
                                            source.Execute(scope);
                                        } catch (Exception ex) { Console.WriteLine(script); Console.WriteLine(ex.Message); }
                                    });
                                });
                                sb = new StringBuilder();
                            }
                            Buffer = new byte[Utils.BufferSize];
                            readReset.Set();
                        }, this);
                        readReset.WaitOne();
                    } catch (Exception ex) { Console.WriteLine(ex.Message); }
                }
            }, cts.Token);
            
            // Set Name
            DefaultHandler.SetName(this.Name);
            return true;
        }

        private bool CertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
            return true;
        }

        public void Send(Message msg) {
            queue.Add(msg);
        }

        public void AddHandler(IMsgHandler handler) {
            handler.Parent = this;
            Handlers.Add(handler);
        }
    }
}
